package i18n

import (
	"embed"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"os"
	"path"
	"path/filepath"
	"strings"
	"unicode/utf8"

	"github.com/pelletier/go-toml/v2"
	"gopkg.in/yaml.v3"
)

var (
	ErrLoadBundleNil      = errors.New("i18n.Loader: bundle is nil")
	ErrLoadFolderEmpty    = errors.New("i18n.Loader: file folder is empty")
	ErrLoadFolderNotExist = errors.New("i18n.Loader: file folder not exist")
	ErrLoadFilePathEmpty  = errors.New("i18n.Loader: file path is empty")
	ErrLoadFileNotExist   = errors.New("i18n.Loader: file not exist")
	ErrLoadIoNil          = errors.New("i18n.Loader: io.Reader is nil")
	ErrLoadIoWith         = func(err error) error {
		return fmt.Errorf("i18n.Loader: io.Reader error, %v", err)
	}
	ErrLoadInvalidFormat   = errors.New("i18n.Loader: format is invalid, only supports 'JSON', 'YAML' or 'TOML'")
	ErrLoadLangNameEmpty   = errors.New("i18n.Loader: langName is empty")
	ErrLoadLangNameExtract = errors.New("i18n.Loader: Failed to extract langName from file path")
	ErrLoadLangNameInvalid = errors.New("i18n.Loader: langName is invalid, must be like 'en', 'en-US', 'zh' or 'zh-CN'")
	ErrLoadContentEmpty    = errors.New("i18n.Loader: content is empty")
	ErrLoadErrorEncoding   = errors.New("i18n.Loader: encoding error, only supports UTF-8")
	ErrLoadErrorWith       = func(err error) error {
		return fmt.Errorf("i18n.Loader: %v", err)
	}
)

// LoadWithFolder 从本地文件夹中加载语言包数据到默认的多语言包管理器
//   - folder 文件夹路径, 支持JSON、YAML和Toml格式编码为UTF8
//   - 自动读取文件夹中的语言文件, 仅读取指定文件夹及下一级文件夹中的文件
//   - 自动从文件路径中解析语言名称(标签), 文件名优先，文件夹名次选
//   - 如 "folder/groupName.en-US.json", "folder/en-US.yml", "folder/en-US/fileName.json",
//   - 如果文件夹为空或不存在JSON、YAML和Toml文件，则什么也不做
//   - 如果已加载过同名语言/同名KEY的语言包消息，则合并语言包数据
func LoadWithFolder(folder string) error {
	return GetDefaultBundle().LoadWithFolder(folder)
}

// LoadWithFolder 从本地文件夹中加载语言包数据到当前多语言包管理器
//   - folder 文件夹路径, 支持JSON、YAML和Toml格式编码为UTF8
//   - 自动读取文件夹中的语言文件, 仅读取指定文件夹及下一级文件夹中的文件
//   - 自动从文件路径中解析语言名称(标签), 文件名优先，文件夹名次选
//   - 如 "folder/groupName.en-US.json", "folder/en-US.yml", "folder/en-US/fileName.json",
//   - 如果文件夹为空或不存在JSON、YAML和Toml文件，则什么也不做
//   - 如果已加载过同名语言/同名KEY的语言包消息，则合并语言包数据
func (b *Bundle) LoadWithFolder(folder string) error {
	if b == nil {
		return ErrLoadBundleNil
	}
	folder = strings.TrimSpace(folder)
	folder = path.Clean(folder)
	if folder == "" {
		return ErrLoadFolderEmpty
	}
	dirs, err := os.ReadDir(folder)
	if err != nil {
		GetLogger().Error("i18n.Loader: LoadWithFolder", slog.String("error", err.Error()))
		return ErrLoadFolderNotExist
	}
	files := []string{}
	for _, v := range dirs {
		if v.IsDir() {
			// 读取子文件夹中的文件
			if subDirs, err := os.ReadDir(path.Join(folder, v.Name())); err == nil {
				for _, sv := range subDirs {
					if !sv.IsDir() {
						switch strings.ToUpper(path.Ext(sv.Name())) {
						case ".JSON", ".YML", ".YAML", ".TOML":
							files = append(files, path.Join(folder, v.Name(), sv.Name()))
						default:
							continue
						}
					}
				}
			}
			continue
		}
		switch strings.ToUpper(path.Ext(v.Name())) {
		case ".JSON", ".YML", ".YAML", ".TOML":
			files = append(files, path.Join(folder, v.Name()))
		default:
			continue
		}
	}
	for _, file := range files {
		if err := b.LoadWithFile(file); err != nil {
			return err
		}
	}
	return nil
}

// LoadWithFile 从指定的明确本地路径文件加载语言包数据到默认的多语言包管理器
//   - filePath 文件路径, 支持JSON、YAML和Toml格式编码为UTF8
//   - 自动从文件路径中解析语言名称(标签), 文件名优先，文件夹名次选
//   - 如 "folder/groupName.en-US.json", "folder/en-US.yml", "folder/en-US/fileName.json",
//   - 如果已加载过同名语言/同名KEY的语言包消息，则合并语言包数据
func LoadWithFile(filePath string) error {
	return GetDefaultBundle().LoadWithFile(filePath)
}

// LoadWithFile 从指定的明确本地路径文件加载语言包数据到当前多语言包管理器
//   - filePath 文件路径, 支持JSON、YAML和Toml格式编码为UTF8
//   - 自动从文件路径中解析语言名称(标签), 文件名优先，文件夹名次选
//   - 如 "folder/groupName.en-US.json", "folder/en-US.yml", "folder/en-US/fileName.json",
//   - 如果已加载过同名语言/同名KEY的语言包消息，则合并语言包数据
func (b *Bundle) LoadWithFile(filePath string) error {
	if b == nil {
		return ErrLoadBundleNil
	}
	if len(filePath) == 0 {
		return ErrLoadFilePathEmpty
	}
	langName := _loadFilePathExtractLangName(filePath)
	if len(langName) == 0 {
		return ErrLoadLangNameExtract
	}
	if vs, err := ParseLangName(langName); err != nil {
		return ErrLoadLangNameInvalid
	} else {
		langName = vs
	}
	if err := _loadWithFile(filePath, b, langName); err != nil {
		GetLogger().Error("i18n.Loader: load file error", slog.String("error", err.Error()))
		return err
	} else {
		GetLogger().Info("i18n.Loader: load file success", slog.String("file", filePath))
	}
	return nil
}

func _loadWithFile(filePath string, bundle *Bundle, langName string) error {
	// 依据文件扩展名自动识别语言包格式，支持JSON、YAML和Toml格式编码为UTF8
	format := strings.ToUpper(strings.TrimPrefix(path.Ext(filePath), "."))
	switch format {
	case "JSON", "TOML", "YAML", "YML":
	default:
		return ErrLoadInvalidFormat
	}
	// 获取文件 Reader (O_RDONLY)
	file, err := os.Open(filePath)
	if err != nil {
		GetLogger().Error("i18n.Loader: os.Open error", slog.String("error", err.Error()))
		return ErrLoadErrorWith(err)
	}
	defer file.Close()
	return _loadWithReader(file, bundle, langName, format)
}
func _loadFilePathExtractLangName(filePath string) string {
	// 从文件名中解析语言名称(标签)
	//   - 自动从文件路径中解析语言名称(标签), 文件名优先，文件夹名次选
	langs := strings.Split(filepath.Base(filePath), ".")
	if len(langs) > 1 && (len(langs[len(langs)-2]) == 2 || strings.Contains(langs[len(langs)-2], "-")) {
		// "en-US.yml", "folder/zh-CN.json"
		return langs[len(langs)-2]
	} else {
		// 从文件夹名中解析语言名称(标签)
		// "zh-CN/fileName.json", "folder/en-US/fileName.json",
		langs = strings.Split(filePath, "/")
		if len(langs) > 1 && (len(langs[len(langs)-2]) == 2 || strings.Contains(langs[len(langs)-2], "-")) {
			return langs[len(langs)-2]
		}
	}
	return ""
}

// LoadWithEmbedFS 从EmbedFS文件夹中加载语言包数据到默认多语言包管理器
//   - folder 文件夹路径, 支持JSON、YAML和Toml格式编码为UTF8
//   - 自动读取文件夹中的语言文件, 仅读取指定文件夹及下一级文件夹中的文件
//   - 自动从文件路径中解析语言名称(标签), 文件名优先，文件夹名次选
//   - 如 "folder/groupName.en-US.json", "folder/en-US.yml", "folder/en-US/fileName.json",
//   - 如果文件夹为空或不存在JSON、YAML和Toml文件，则什么也不做
//   - 如果已加载过同名语言/同名KEY的语言包消息，则合并语言包数据
func LoadWithEmbedFS(fs embed.FS, folder string) error {
	return GetDefaultBundle().LoadWithEmbedFS(fs, folder)
}

// LoadWithEmbedFS 从EmbedFS文件夹中加载语言包数据到当前多语言包管理器
//   - folder 文件夹路径, 支持JSON、YAML和Toml格式编码为UTF8
//   - 自动读取文件夹中的语言文件, 仅读取指定文件夹及下一级文件夹中的文件
//   - 自动从文件路径中解析语言名称(标签), 文件名优先，文件夹名次选
//   - 如 "folder/groupName.en-US.json", "folder/en-US.yml", "folder/en-US/fileName.json",
//   - 如果文件夹为空或不存在JSON、YAML和Toml文件，则什么也不做
//   - 如果已加载过同名语言/同名KEY的语言包消息，则合并语言包数据
func (b *Bundle) LoadWithEmbedFS(fs embed.FS, folder string) error {
	if b == nil {
		return ErrLoadBundleNil
	}
	folder = strings.TrimSpace(folder)
	folder = path.Clean(folder)
	if folder == "" {
		return ErrLoadFolderEmpty
	}
	dirs, err := fs.ReadDir(folder)
	if err != nil {
		GetLogger().Error("i18n.Loader: LoadWithEmbedFS", slog.String("error", err.Error()))
		return ErrLoadFolderNotExist
	}
	files := []string{}
	for _, v := range dirs {
		if v.IsDir() {
			// 读取子文件夹中的文件
			if subDirs, err := fs.ReadDir(path.Join(folder, v.Name())); err == nil {
				for _, sv := range subDirs {
					if !sv.IsDir() {
						switch strings.ToUpper(path.Ext(sv.Name())) {
						case ".JSON", ".YML", ".YAML", ".TOML":
							files = append(files, path.Join(folder, v.Name(), sv.Name()))
						default:
							continue
						}
					}
				}
			}
			continue
		}
		switch strings.ToUpper(path.Ext(v.Name())) {
		case ".JSON", ".YML", ".YAML", ".TOML":
			files = append(files, path.Join(folder, v.Name()))
		default:
			continue
		}
	}
	for _, filePath := range files {
		langName := _loadFilePathExtractLangName(filePath)
		if len(langName) == 0 {
			return ErrLoadLangNameExtract
		}
		if vs, err := ParseLangName(langName); err != nil {
			return ErrLoadLangNameInvalid
		} else {
			langName = vs
		}
		// 依据文件扩展名自动识别语言包格式，支持JSON、YAML和Toml格式编码为UTF8
		format := strings.ToUpper(strings.TrimPrefix(path.Ext(filePath), "."))
		switch format {
		case "JSON", "TOML", "YAML", "YML":
		default:
			return ErrLoadInvalidFormat
		}
		// 获取文件 Reader (O_RDONLY)
		file, err := fs.Open(filePath)
		if err != nil {
			GetLogger().Error("i18n.Loader: embed.FS/fs.Open error", slog.String("error", err.Error()))
			return ErrLoadErrorWith(err)
		}
		defer file.Close()
		if err := _loadWithReader(file, b, langName, format); err != nil {
			GetLogger().Error("i18n.Loader: embed.FS/Reader error", slog.String("error", err.Error()))
			return err
		} else {
			GetLogger().Info("i18n.Loader: succeed", slog.String("filePath", filePath))
		}
	}
	return nil
}

// LoadWithReader 从Reader加载语言包数据到默认的多语言包管理器
//   - reader io.Reader 读取器，可以是文件、内存等数据源的读取器
//   - langName 指定明确的语言名称(标签), 如 "en-US"、"zh-CN"
//   - format 指定明确的数据格式，如 "JSON","YAML"/"YML","TOML" (不区分大小写)
//   - 如果已加载过同名语言/同名KEY的语言包消息，则合并语言包数据
func LoadWithReader(reader io.Reader, langName, format string) error {
	return GetDefaultBundle().LoadWithReader(reader, langName, format)
}

// LoadWithReader 从Reader加载语言包数据到当前多语言包管理器
//   - reader io.Reader 读取器，可以是文件、内存等数据源的读取器
//   - langName 指定明确的语言名称(标签), 如 "en-US"、"zh-CN"
//   - format 指定明确的数据格式，如 "JSON","YAML"/"YML","TOML" (不区分大小写)
//   - 如果已加载过同名语言/同名KEY的语言包消息，则合并语言包数据
func (b *Bundle) LoadWithReader(reader io.Reader, langName, format string) error {
	if b == nil {
		return ErrLoadBundleNil
	}
	if reader == nil {
		return ErrLoadIoNil
	}
	if len(langName) == 0 {
		return ErrLoadLangNameEmpty
	}
	langName, err := ParseLangName(langName)
	if err != nil {
		return ErrLoadLangNameInvalid
	}
	format = strings.ToUpper(strings.TrimPrefix(format, "."))
	switch format {
	case "JSON", "TOML", "YAML", "YML":
	default:
		return ErrLoadInvalidFormat
	}
	if err := _loadWithReader(reader, b, langName, format); err != nil {
		GetLogger().Error("i18n.Loader: LoadWithReader", slog.String("error", err.Error()))
		return err
	}
	return nil
}
func _loadWithReader(reader io.Reader, bundle *Bundle, langName, format string) error {
	dataBytes, err := io.ReadAll(reader)
	if err != nil {
		return ErrLoadIoWith(err)
	}
	if len(dataBytes) == 0 {
		return ErrLoadContentEmpty
	}
	if !utf8.Valid(dataBytes) {
		return ErrLoadErrorEncoding
	}
	switch format {
	case "JSON":
		return _loadWithJson(dataBytes, bundle, langName)
	case "TOML":
		return _loadWithToml(dataBytes, bundle, langName)
	case "YAML", "YML":
		return _loadWithYaml(dataBytes, bundle, langName)
	default:
		return ErrLoadInvalidFormat
	}
}
func _loadWithJson(data []byte, bundle *Bundle, langName string) error {
	mapAny := make(map[string]any)
	if err := json.Unmarshal(data, &mapAny); err != nil {
		return ErrLoadErrorWith(err)
	}
	return _loadSetMessages(mapAny, bundle, langName)
}
func _loadWithToml(data []byte, bundle *Bundle, langName string) error {
	mapAny := make(map[string]any)
	if err := toml.Unmarshal(data, &mapAny); err != nil {
		return ErrLoadErrorWith(err)
	}
	return _loadSetMessages(mapAny, bundle, langName)
}
func _loadWithYaml(data []byte, bundle *Bundle, langName string) error {
	mapAny := make(map[string]any)
	if err := yaml.Unmarshal(data, &mapAny); err != nil {
		return ErrLoadErrorWith(err)
	}
	return _loadSetMessages(mapAny, bundle, langName)
}
func _loadSetMessages(data map[string]any, bundle *Bundle, langName string) error {
	if mapMessage, err := MapAnyToMessages(data); err != nil {
		return ErrLoadErrorWith(err)
	} else if err := bundle.SetMessages(langName, mapMessage); err != nil {
		return ErrLoadErrorWith(err)
	}
	return nil
}
